{*******************************************************************************
* The contents of this file are used with permission, subject to the Mozilla   *
* Public License Version 1.1 (the "License"); you may not use this file except *
* in compliance with the License. You may obtain a copy of the License at      *
* http://www.mozilla.org/MPL/                                                  *
*                                                                              *
* Software distributed under the License is distributed on an "AS IS" basis,   *
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for *
* the specific language governing rights and limitations under the License.    *
*                                                                              *
* The Original Code is distributed as part of the "mediate" product and is     *
* Copyright (C) @home media limited. All Rights Reserved.                      *
*                                                                              *
* Email: support@athomemedia.co.uk                                             *
* Web:   http://www.athomemedia.co.uk                                          *
*******************************************************************************}

unit Containers;

interface

uses Classes, Graphics, UIObjects, Colour, Places, Backgrounds, Foregrounds,
     BaseControls, Viewport, XMLHelper, AHMTypes;


const
  DEFAULT_CNTR_WIDTH = 200; // Default width for new container
  DEFAULT_CNTR_HEIGHT = 100; // Default height for new container

{$IFDEF DESIGNER}
  MODIFY_CAPTION = 'Edit Controls';

  // Property categories and hints for designer
  HINT_WIDTH = 'specifies width for the container';
  HINT_HEIGHT = 'specifies height for the container';
  HINT_BACKGROUND = 'specifies a background object for the container';
  HINT_FOREGROUND = 'specifies a foreground object for the container';
  HINT_CONTROLS = 'specifies a list of controls to display in the container';
  HINT_CLIPPING = 'controls whether controls are clipped to the container dimensions';
  HINT_EFFECT = 'defines visual effects for this container and its controls';
{$ENDIF}

type
  TAHMContainer = class(TAHMAbstractControl)
  private
    FBackground: TAHMBackground;
    FForeground: TAHMForeground;
    FControls: TAHMControlList;
    FClipping: Boolean;
    FPreviewDown: Boolean;
    FPreviewControl: TAHMInteractiveControl;
  protected
    function GetGamma: TAHMCanvasColour; override;
    function GetXScale: Single; override;
    function GetYScale: Single; override;
    function GetDesignerWidth: Integer; override;
    function GetDesignerHeight: Integer; override;
    function GetDesignerSize: String; override;
    procedure SetDesignerSize(Size: String); override;
    procedure ControlsModified(Sender: TObject);
    procedure ClearProperties; override;
  public
    constructor Create(const Name: String); override;
    destructor Destroy; override;
    property Viewport: TAHMViewport read FViewport;
    function DesignerAdjustSize(DX, DY: Integer): Boolean; override;
    function PreviewMouseDown(X, Y: Single): Boolean; override;
    function PreviewMouseMove(X, Y: Single; Down: Boolean): Boolean; override;
    function PreviewMouseUp(X, Y: Single): Boolean; override;
    function PreviewMouseWheel(Delta: Integer): Boolean; override;
    function ControlAtCoordinate(X, Y: Single): TAHMInteractiveControl;
    function RemoveAbstractControl(Remove: TAHMAbstractControl): TAHMUIObject;
    function FindAbstractControl(X, Y: Single; var Ignore: TAHMAbstractControl): TAHMAbstractControl;
    function FindControl(X, Y: Single; var Ignore: TAHMControl): TAHMControl;
    function CanvasRect(X, Y, Width, Height: Single): TAHMRectF; override;
    function ClipRect: TAHMRectF; override;
    function BoundsPoint(XPos, YPos: Single): Boolean; override;
    function ModifyActions: Integer; override;
    function Modify(Action: Integer): Boolean; override;
    function ModifyCaption(Action: Integer): String; override;
    procedure MoveControl(XPos, YPos: Single); override;
    procedure SizeControl(Width, Height: Single); override;
    procedure Invalidate; override;
    procedure InvalidateUIObject(UIObject: TAHMUIObject); override;
    procedure Render(Viewport: TAHMViewport); override;
    procedure Resize; override;
    procedure SeedObject; override;
    procedure Assign(Source: TPersistent); override;
    procedure SaveAsXML(Lines: TStrings); override;
    procedure ParseFromXML(XML: TAHMXMLHelper); override;
    procedure DeleteUIObject(UIObject: TAHMUIObject); override;
    procedure BuildProperties(Properties: TAHMUIPropertyList); override;
  published
    property Background: TAHMBackground read FBackground write FBackground;
    property Foreground: TAHMForeground read FForeground write FForeground;
    property Controls: TAHMControlList read FControls;
    property Clipping: Boolean read FClipping write FClipping;
  end;

  TAHMContainerList = class(TAHMUIObjectList)
  private
  protected
  public
    constructor Create; override;
    function AddContainer(const InheritFrom, NewName: String): TAHMContainer;
    function GetContainer(const Name: String): TAHMContainer;
  end;


implementation

uses SysUtils, Types, ApplicationManager, Screen, Logging, Screens, Canvas,
     Effects, {$IFDEF DESIGNER}DesignerList,{$ENDIF} Utilities, Math, TagNames,
     XMLTags, ErrorCodes;

constructor TAHMContainerList.Create;
begin
  inherited Create;

  XMLTagName := TAG_CONTAINERS;
  ChildClass := TAHMContainer;
end;

function TAHMContainerList.AddContainer(const InheritFrom, NewName: String): TAHMContainer;
begin
  Result := TAHMContainer(Inherit(InheritFrom, NewName));
end;

function TAHMContainerList.GetContainer(const Name: String): TAHMContainer;
begin
  Result := TAHMContainer(GetObject(Name));
end;

//----------------------------------------------------------------------------//

constructor TAHMContainer.Create(const Name: String);
begin
  inherited Create(Name);

  XMLTagName := TAG_CONTAINER;
  ObjectType := otContainers;
  ImageIndex := Ord(otContainers);
  IsPreviewable := True;

  FControls := TAHMControlList.Create;
  FControls.DefaultSort := lsNatural;
  FControls.OnModified := ControlsModified;

  // Don't create a viewport for our empty control in initialization section
  if Name <> '' then
  begin
    FViewport := TAHMViewport.Create;
    FViewport.Owner := Self;
  end;
end;

destructor TAHMContainer.Destroy;
begin
  FControls.Free;
  FViewport.Free;

  inherited Destroy;
end;

function TAHMContainer.GetGamma: TAHMCanvasColour;
begin
  // Return uncorrected gamma
  Result := OwnGamma;
end;

function TAHMContainer.GetXScale: Single;
begin
  // Return uncorrected horizontal scale
  Result := OwnXScale;
end;

function TAHMContainer.GetYScale: Single;
begin
  // Return uncorrected vertical scale
  Result := OwnYScale;
end;

function TAHMContainer.GetDesignerWidth: Integer;
begin
  // Retrieve virtual container width for designer
  Result := VirtualWidth;
end;

function TAHMContainer.GetDesignerHeight: Integer;
begin
  // Retrieve virtual container height for designer
  Result := VirtualHeight;
end;

function TAHMContainer.GetDesignerSize: String;
begin
  // Return current width/height in concatenated string format
  Result := Format('%d,%d', [VirtualWidth, VirtualHeight]);
end;

procedure TAHMContainer.SetDesignerSize(Size: String);
var
  W, H: Integer;
begin
  if ParseStringValues(Size, W, H) then
  begin
    VirtualWidth := W;
    VirtualHeight := H;
  end;
end;

procedure TAHMContainer.ControlsModified(Sender: TObject);
var
  i: Integer;
begin
  // Handle layout changes when control list is updated
  for i := 0 to Pred(FControls.Count) do
    TAHMControl(FControls.Objects[i]).InvalidateSize;

  // Clear preview control and associated properties
  FPreviewControl := nil;
  FPreviewDown := False;
end;

procedure TAHMContainer.ClearProperties;
begin
  inherited;

  // Clear custom container properties
  FBackground := nil;
  FForeground := nil;
  Effect := nil;
  FControls.Clear;
  FClipping := False;
end;

function TAHMContainer.DesignerAdjustSize(DX, DY: Integer): Boolean;
var
  NewWidth, NewHeight: Integer;
begin
  // Update size in response to designer adjustment - clamp negative values
  NewWidth := Max(0, VirtualWidth + DX);
  NewHeight := Max(0, VirtualHeight + DY);
  if DX <> 0 then VirtualWidth := NewWidth;
  if DY <> 0 then VirtualHeight := NewHeight;
  if (DX <> 0) or (DY <> 0) then Modified := True;

  Result := False;
end;

function TAHMContainer.PreviewMouseDown(X, Y: Single): Boolean;
begin
  // Record control (if any) beneath mouse when button was pressed down
  FPreviewControl := ControlAtCoordinate(X, Y);
  FPreviewDown := Assigned(FPreviewControl);

  // Pass mouse down event to control if we found one
  if Assigned(FPreviewControl) then FPreviewControl.MouseDown(X, Y);
  Result := True;
end;

function TAHMContainer.PreviewMouseMove(X, Y: Single; Down: Boolean): Boolean;
begin
  // If mouse button not held down then we can focus control beneath mouse
  if not FPreviewDown then
  begin
    if Assigned(FPreviewControl) then FPreviewControl.Focused := False;
    FPreviewControl := ControlAtCoordinate(X, Y);
    if Assigned(FPreviewControl) then FPreviewControl.Focused := True;
  end;

  // Pass mouse move method to control to allow mouse tracking
  if Assigned(FPreviewControl) then FPreviewControl.MouseMove(X, Y, Down);
  Result := True;
end;

function TAHMContainer.PreviewMouseUp(X, Y: Single): Boolean;
begin
  // Pass mouse up method to mouse control if we have one
  if Assigned(FPreviewControl) then FPreviewControl.MouseUp(X, Y);

  // Reset mouse down flag
  FPreviewDown := False;
  Result := True;
end;

function TAHMContainer.PreviewMouseWheel(Delta: Integer): Boolean;
begin
  // Pass mouse wheel method to mouse control if we have one
  if Assigned(FPreviewControl) then FPreviewControl.MouseWheel(Delta);
  Result := True;
end;

function TAHMContainer.ControlAtCoordinate(X, Y: Single): TAHMInteractiveControl;
var
  i: Integer;
  Depth: TAHMDepthType;
  oControl: TAHMControl;
begin
  // Set default result - no control found
  Result := nil;

  // Iterate through controls in reverse draw order
  for Depth := dtForeground downto dtBackground do
    for i := Pred(FControls.Count) downto 0 do
    begin
      oControl := TAHMControl(FControls.Objects[i]);

      // Is this a visible interactive mouse control that bounds our point?
      if (oControl.Depth = Depth) and (oControl is TAHMInteractiveControl) and
         oControl.Visible and TAHMInteractiveControl(oControl).MouseEnabled and
         oControl.BoundsPoint(X, Y) then
      begin
        // We found a control
        Result := TAHMInteractiveControl(oControl);
        Exit;
      end;
    end;
end;

function TAHMContainer.RemoveAbstractControl(Remove: TAHMAbstractControl): TAHMUIObject;
var
  i: Integer;
begin
  // If we own the specified control then delete it and return owner
  i := FControls.IndexOf(Remove);
  if i < 0 then
    Result := nil
  else
  begin
    FControls.Delete(i);
    Result := Self;
  end;
end;

function TAHMContainer.FindAbstractControl(X, Y: Single; var Ignore: TAHMAbstractControl): TAHMAbstractControl;
begin
  if Ignore = Self then
  begin
    // This container is being ignored, but don't ignore any others
    Result := nil;
    Ignore := nil;
  end
  else if Ignore is TAHMContainer then
  begin
    // Ignoring containers, but not found right container yet
    Result := nil;
  end
  else if Ignore is TAHMControl then
  begin
    // Iterating controls, see if control is in this container
    Result := FindControl(X, Y, TAHMControl(Ignore));

    // If we run out of controls in this container, return the container
    if (Result = nil) and (Ignore = nil) and BoundsPoint(X, Y) then Result := Self;
  end
  else
  begin
    // Ignore parameter must be nil or an invalid type
    Ignore := nil;

    // See if we can find a control in this container
    Result := FindControl(X, Y, TAHMControl(Ignore));

    // If no matching control then return the container
    if (Result = nil) and BoundsPoint(X, Y) then Result := Self;
  end;
end;

function TAHMContainer.FindControl(X, Y: Single; var Ignore: TAHMControl): TAHMControl;
var
  i: Integer;
  Depth: TAHMDepthType;
  oControl: TAHMControl;
begin
  // Set default result - no control found
  Result := nil;

  // Iterate through controls in reverse draw order
  for Depth := dtForeground downto dtBackground do
    for i := Pred(FControls.Count) downto 0 do
    begin
      oControl := TAHMControl(FControls.Objects[i]);

      if oControl.Depth = Depth then
      begin
        // Skip all controls until we find our ignore control
        if Ignore <> nil then
        begin
          if Ignore = oControl then Ignore := nil;
          Continue;
        end;

        // Is this a control that bounds our point?
        if oControl.BoundsPoint(X, Y) then
        begin
          // We found a control
          Result := oControl;
          Exit;
        end;
      end;
    end;
end;

function TAHMContainer.CanvasRect(X, Y, Width, Height: Single): TAHMRectF;
begin
  // Container canvas rect is already absolute - not relative to viewport
  Result := MakeRectF(X, Y, Width, Height);
end;

function TAHMContainer.ClipRect: TAHMRectF;
begin
  // Bounding rectangle for container in screen coordinates
  Result := MakeRectF(FXPos, FYPos, FWidth, FHeight);
end;

function TAHMContainer.BoundsPoint(XPos, YPos: Single): Boolean;
begin
  // Check whether passed point is within container bounds
  Result := (XPos >= FXPos) and (YPos >= FYPos) and
            (XPos <= FXPos + FWidth) and (YPos <= FYpos + FHeight);
end;

function TAHMContainer.ModifyActions: Integer;
begin
  // Supports single action for editing controls
  Result := 1;
end;

function TAHMContainer.Modify(Action: Integer): Boolean;
begin
{$IFDEF DESIGNER}
  // Edit controls with list dialog
  with ApplicationManagerInstance.ThemeManager.InterfaceDescriptor do
    Result := TfrmObjectList.ShowList(FControls, ControlList, nil);
  if Result then DoModified;
{$ELSE}
  Result := False;
{$ENDIF}
end;

function TAHMContainer.ModifyCaption(Action: Integer): String;
begin
{$IFDEF DESIGNER}
  Result := MODIFY_CAPTION;
{$ELSE}
  Result := '';
{$ENDIF}
end;

procedure TAHMContainer.MoveControl(XPos, YPos: Single);
begin
  // Call inherited method to move container position
  inherited;

  // Calculate absolute midpoint of our container
  FCentreX := FXPos + (FWidth / 2);
  FCentreY := FYPos + (FHeight / 2);

  // Now update viewport origin properties so it stays in sync
  FViewport.XOrigin := FXPos;
  FViewport.YOrigin := FYPos;
end;

procedure TAHMContainer.SizeControl(Width, Height: Single);
begin
  // Call inherited method to set container size
  inherited;

  // Calculate absolute midpoint of our container
  FCentreX := FXPos + (FWidth / 2);
  FCentreY := FYPos + (FHeight / 2);

  // Now update viewport dimension properties so it stays in sync
  FViewport.Width := FWidth;
  FViewport.Height := FHeight;
end;

procedure TAHMContainer.Invalidate;
var
  i: Integer;
begin
  // Call inherited method to reset valid property
  inherited Invalidate;

  // Now invalidate all contained controls
  for i := 0 to Pred(FControls.Count) do
    TAHMControl(FControls.Objects[i]).Invalidate;
end;

procedure TAHMContainer.InvalidateUIObject(UIObject: TAHMUIObject);
begin
  // Determine appropriate type of UIObject change here...

  if (UIObject is TAHMPlace) and (Placement = UIObject) then
  begin
    // Handle placement changes
    InvalidateSize;
  end
  else if (UIObject is TAHMEffect) and (Effect = UIObject) then
  begin
    // Handle effect changes
    InvalidateEffect;
  end;
end;

procedure TAHMContainer.Render(Viewport: TAHMViewport);
var
  i: Integer;
  Depth: TAHMDepthType;
  oControl: TAHMControl;
begin
  // Call inherited method to initialise container if necessary
  inherited Rendering;

  // Don't render if we're not visible
  if not Visible then Exit;

  // Apply rotation transformation to container
  with Viewport.Canvas do
    if (FXRot <> 0) or (FYRot <> 0) or (FZRot <> 0) then
    begin
      // Lock container rotation so controls don't override it
      SetRotation(FXRot, FYRot, FZRot, FCentreX, FCentreY);
      LockRotation;
    end
    else
    begin
      UnlockRotation;
      UnsetRotation;
    end;

  // Enable viewport clipping if container has clipping enabled
  FViewport.Clipping := Clipping;

  // Render container background first if assigned
  if Assigned(FBackground) then
    FBackground.Render(Self);

  // Now render contained controls
  for Depth := dtBackground to dtForeground do
    for i := 0 to Pred(FControls.Count) do
      if TAHMControl(FControls.Objects[i]).Depth = Depth then
      begin
        // Tweak a few control properties temporarily whilst rendering
        oControl := TAHMControl(FControls.Objects[i]);
        oControl.DrawBorder := ApplicationManagerInstance.UIManager.DrawControlBorders;
        if VerticalFlip then
         oControl.VerticalFlip := not oControl.VerticalFlip;
        oControl.Render(FViewport);
        if VerticalFlip then oControl.VerticalFlip := not oControl.VerticalFlip;
      end;

  // Render effect overlay if assigned
  if Assigned(Effect) and Assigned(Effect.Bitmap) then
    Effect.Bitmap.Render(Self);

  // Render container foreground last if assigned
  if Assigned(FForeground) then
    FForeground.Render(Self);

  // Render borders for display in designer
  if ApplicationManagerInstance.UIManager.DrawContainerBorders then
    with ScreenInstance.Viewport.Canvas do
    begin
      PenWidth := 0.004;
      PenColour := TAHMCanvasColour.Black;
      Rectangle(ClipRect, Gamma, XScale, YScale);
      PenWidth := 0.002;
      PenColour := TAHMCanvasColour.Grey;
      Rectangle(ClipRect, Gamma, XScale, YScale);
    end;
end;

procedure TAHMContainer.Resize;
begin
  inherited Resized;

  // Determine container position and sizing
  if Assigned(Placement) then
  begin
    // Use placement object to apply position and size
    with FViewport do
      Placement.ApplyPosition(FXPos, FYPos, FWidth, FHeight, ScreenWidth, ScreenHeight);
  end
  else
  begin
    // No placement, so our position defaults to the origin
    FXPos := 0;
    FYPos := 0;
  end;

  // Determine container flipping & rotation
  if Assigned(Placement) then
  begin
    // Determine vertical/horizontal flipping
    FXFlip := Placement.HorizontalFlip;
    FYFlip := Placement.VerticalFlip;

    // Determine rotation
    FXRot := Placement.RotationX;
    FYRot := Placement.RotationY;
    FZRot := Placement.RotationZ;
  end;

  // Populate viewport width, height & origin properties
  with Viewport do
  begin
    Width := FWidth;
    Height := FHeight;
    XOrigin := FXPos;
    YOrigin := FYPos;
  end;
end;

procedure TAHMContainer.SeedObject;
begin
{$IFDEF DESIGNER}
  if Ancestor = nil then
  begin
    // Set a default size for this container if none specified
    if (VirtualWidth = 0) or (VirtualHeight = 0) then
    begin
      VirtualWidth := DEFAULT_CNTR_WIDTH;
      VirtualHeight := DEFAULT_CNTR_HEIGHT;
    end;
  end;
{$ENDIF}
end;

procedure TAHMContainer.Assign(Source: TPersistent);
begin
  inherited Assign(Source);

  with Source as TAHMContainer do
  begin
    Self.Background := Background;
    Self.Foreground := Foreground;
    Self.Controls.Assign(Controls);
    Self.Clipping := Clipping;
    Self.Effect := Effect;
    Self.VirtualWidth := VirtualWidth;
    Self.VirtualHeight := VirtualHeight;
  end;
end;

procedure TAHMContainer.SaveAsXML(Lines: TStrings);
var
  oAncs: TAHMContainer;
  sWork: String;
  i: Integer;
begin
  // Determine which ancestor to compare against - real or empty
  oAncs := TAHMContainer(ObjectToCompare);

  // We need to explicitly save comments in overridden method
  SaveXMLComments(Lines);

  // Only populate attributes that differ from our ancestor
  Lines.Add(TABS[2] + TagBegin);

  // Do we need to store size?
  if ((VirtualWidth <> 0) and (VirtualWidth <> oAncs.VirtualWidth)) or
     ((VirtualHeight <> 0) and (VirtualHeight <> oAncs.VirtualHeight)) then
    Lines.Add(TABS[3] + TGB+TAG_SIZE+TGC+ IntToStr(VirtualWidth) + ',' +
              IntToStr(VirtualHeight) + TGE+TAG_SIZE+TGC);

  // Do we need depth element?
  if (FDepth <> dtMidground) and (FDepth <> oAncs.Depth) then
    Lines.Add(TABS[3] + TGB+TAG_DEPTH+TGC + Depth2Str(FDepth) + TGE+TAG_DEPTH+TGC);

  // Do we need placement element?
  if (Placement <> nil) and (Placement <> oAncs.Placement) then
    Lines.Add(TABS[3] + Placement.XMLReference(TAG_PLACEMENT));

  // Do we need background or foreground elements?
  if (FBackground <> nil) and (FBackground <> oAncs.Background) then
    Lines.Add(TABS[3] + FBackground.XMLReference);
  if (FForeground <> nil) and (FForeground <> oAncs.Foreground) then
    Lines.Add(TABS[3] + FForeground.XMLReference);

  // Do we need to store controls?
  if FControls.IsDifferent(oAncs.Controls) then
  begin
    Lines.Add(TABS[3] + TGB+TAG_CONTENTS+TGC);
    for i := 0 to Pred(FControls.Count) do
      if not oAncs.Controls.Contains(FControls.Objects[i]) then
        Lines.Add(TABS[4] + TAHMControl(FControls.Objects[i]).XMLReference);
    Lines.Add(TABS[3] + TGE+TAG_CONTENTS+TGC);
  end;

  // Do we need status element?
  if (not InitiallyVisible) or (InitiallyVisible <> oAncs.InitiallyVisible) then
  begin
    sWork := TGB+TAG_STATUS+SPC+ATTR_VISIBLE+ATB + Bool2Str(InitiallyVisible);
    Lines.Add(TABS[3] + sWork + ATE);
  end;

  // Do we need effect elements?
  if (Effect <> nil) and (Effect <> oAncs.Effect) then
    Lines.Add(TABS[3] + Effect.XMLReference);

  // Do we need to store properties?
  if FClipping and (FClipping <> oAncs.Clipping) then
  begin
    sWork := TGB+TAG_PROPERTIES+SPC+ATTR_CLIPPING+ATB + Bool2Str(FClipping);
    Lines.Add(TABS[3] + sWork + ATE)
  end;

  Lines.Add(TABS[2] + TagEnd);
end;

procedure TAHMContainer.ParseFromXML(XML: TAHMXMLHelper);
var
  sName, sProp, sVisible: String;
  oControl: TAHMControl;
  iX, iY: Integer;
  ContainerNode, PropsNode, DetailNode: TAHMXMLNode;
begin
  // Initialise parsing nodes
  ContainerNode := XML.CurrentNode;
  PropsNode := nil;

  // Discover additional properties for this object
  with ApplicationManagerInstance.ThemeManager do
    while XML.NextElement(ContainerNode, PropsNode) do
      if XML.CurrentElement = TAG_BACKGROUND then
      begin
        sName := XML.GetAttribute(ATTR_NAME);
        Background := VisualDescriptor.BackgroundList.GetBackground(sName);
      end
      else if XML.CurrentElement = TAG_FOREGROUND then
      begin
        sName := XML.GetAttribute(ATTR_NAME);
        Foreground := VisualDescriptor.ForegroundList.GetForeground(sName);
      end
      else if (XML.CurrentElement = TAG_SIZE) then
      begin
        if XML.GetCoordsValue(iX, iY) then
        begin
          VirtualWidth := iX;
          VirtualHeight := iY;
        end
        else
          LogParsingError(THM_PRS_INT_MALFORMED_CT_SIZE_TAGS, Self);
      end
      else if XML.CurrentElement = TAG_CONTENTS then
      begin
        DetailNode := nil;

        // Parse list of control contents
        while XML.NextElement(PropsNode, DetailNode) do
        begin
          sName := XML.GetAttribute(ATTR_NAME);
          oControl := InterfaceDescriptor.ControlList.GetControl(sName);
          if oControl <> nil then
            Controls.Add(oControl)
          else
            LogParsingError(THM_PRS_INT_INVALID_CONTAINER_CONTROL + sName, Self);
        end;
      end
      else if XML.CurrentElement = TAG_PROPERTIES then
      begin
        // Parse container properties
        sProp := XML.GetAttribute(ATTR_CLIPPING);
        if Str2Bool(sProp) then Clipping := True;
      end
      else if XML.CurrentElement = TAG_STATUS then
      begin
        // Parse container status
        sVisible := XML.GetAttribute(ATTR_VISIBLE);
        if sVisible <> '' then
        begin
          Visible := Str2Bool(sVisible);
          InitiallyVisible := Str2Bool(sVisible);
        end;
      end
      else
      begin
        // Handle common XML tags to all control descendants
        ParseCommonXML(XML);
      end;
end;

procedure TAHMContainer.DeleteUIObject(UIObject: TAHMUIObject);
begin
  inherited;

  // Remove control from list of controls
  if UIObject is TAHMControl then
    FControls.RemoveObject(UIObject);
end;

procedure TAHMContainer.BuildProperties(Properties: TAHMUIPropertyList);
begin
{$IFDEF DESIGNER}
  inherited;

  // Build custom container properties
  Properties.Add('Width', 'VirtualWidth', CAT_DESIGN, HINT_WIDTH);
  Properties.Add('Height', 'VirtualHeight', CAT_DESIGN, HINT_HEIGHT);
  Properties.Add('Controls', 'Controls', CAT_DESIGN, HINT_CONTROLS);
  Properties.Add('Clipping', 'Clipping', CAT_DESIGN, HINT_CLIPPING);
  Properties.Add('Background', 'Background', CAT_APPEARANCE, HINT_BACKGROUND);
  Properties.Add('Foreground', 'Foreground', CAT_APPEARANCE, HINT_FOREGROUND);
  Properties.Add('Effect', 'Effect', CAT_APPEARANCE, HINT_EFFECT);
{$ENDIF}
end;


end.
